/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#ifndef itkPromoteType_h
#define itkPromoteType_h

#include "itkMacro.h"

// Simplification of boost::common_type
namespace itk
{

/// \cond HIDE_META_PROGRAMMING
namespace mpl
{

namespace Details
{

/** Helper class to implement \c itk::PromoteType<>.
 * This struct is meant to be specialized with \c ITK_ASSOCIATE macros for the
 * primitive types, or by the end-developer in order to support overloads.
 * \ingroup MetaProgrammingLibrary
 * \ingroup ITKCommon
 */
template <int VTypeEnum, typename TA, typename TB>
struct SizeToType;

/** Helper class to implement \c itk::PromoteType<>.
 * \ingroup MetaProgrammingLibrary
 * \ingroup ITKCommon
 */
template <int VCharacters>
struct Identity
{
  using Type = char[VCharacters];
};

/** Helper macro to implement \c itk::PromoteType<>.
 * This macros helps specializing \c SizeToType<> for any pair of type.
 * \ingroup MetaProgrammingLibrary
 * \ingroup ITKCommon
 */
#define ITK_ASSOCIATE(VTypeEnum, Typed) \
  template <typename TA, typename TB>   \
  struct SizeToType<VTypeEnum, TA, TB>  \
  {                                     \
    using Type = Typed;                 \
  }

ITK_ASSOCIATE(1, TA);
ITK_ASSOCIATE(2, TB);

ITK_ASSOCIATE(3, short);
ITK_ASSOCIATE(4, unsigned short);
ITK_ASSOCIATE(5, int);
ITK_ASSOCIATE(6, unsigned int);
ITK_ASSOCIATE(7, long);
ITK_ASSOCIATE(8, unsigned long);
ITK_ASSOCIATE(9, long long);
ITK_ASSOCIATE(10, unsigned long long);
ITK_ASSOCIATE(11, float);
ITK_ASSOCIATE(12, double);
ITK_ASSOCIATE(13, long double);
#undef ITK_ASSOCIATE
} // namespace Details

/** Helper class to deduce, in C++98, the resulting type of an operation between two input types.
 * \tparam TA Input type 1
 * \tparam TB Input type 2
 *
 * \c Type the resulting type compatible with \c TA and \c TB.
 *
 * In order to support user defined type, specialize \c
 * itk::PromoteType<> in consequence. For instance, to support type promotion
 * between \c std::complex<>, write:
 \code
 namespace itk {
   template <typename TA, typename TB>
   struct PromoteType<std::complex<TA>, std::complex<TB> > {
     using Type = std::complex<typename PromoteType<TA,TB>::Type>;
   };
 }
 \endcode
 *
 * \ingroup MetaProgrammingLibrary
 * \ingroup ITKCommon
 */
template <typename TA, typename TB>
struct PromoteType
{
  static TA a;
  static TB b;

  // Aimed at supporting overloads
  template <typename T>
  static Details::Identity<1>::Type & Check(typename Details::SizeToType<1, TA, TB>::Type, T);
  template <typename T>
  static Details::Identity<2>::Type & Check(typename Details::SizeToType<2, TA, TB>::Type, T);

  // Common numeric types
  static Details::Identity<3>::Type &
  Check(typename Details::SizeToType<3, TA, TB>::Type, int);
  static Details::Identity<4>::Type &
  Check(typename Details::SizeToType<4, TA, TB>::Type, int);
  static Details::Identity<5>::Type &
  Check(typename Details::SizeToType<5, TA, TB>::Type, int);
  static Details::Identity<6>::Type &
  Check(typename Details::SizeToType<6, TA, TB>::Type, int);
  static Details::Identity<7>::Type &
  Check(typename Details::SizeToType<7, TA, TB>::Type, int);
  static Details::Identity<8>::Type &
  Check(typename Details::SizeToType<8, TA, TB>::Type, int);
  static Details::Identity<9>::Type &
  Check(typename Details::SizeToType<9, TA, TB>::Type, int);
  static Details::Identity<10>::Type &
  Check(typename Details::SizeToType<10, TA, TB>::Type, int);
  static Details::Identity<11>::Type &
  Check(typename Details::SizeToType<11, TA, TB>::Type, int);
  static Details::Identity<12>::Type &
  Check(typename Details::SizeToType<12, TA, TB>::Type, int);
  static Details::Identity<13>::Type &
  Check(typename Details::SizeToType<13, TA, TB>::Type, int);

public:
  /** Type result of operations between \c TA and \c TB.
   * \internal
   * We let the compiler deduce the type resulting of the operation `a+b`. As
   * the standard says (see the extract in the test file itkPromoteType.cxx),
   * <em>«arithmetic operators do not accept types smaller than int as
   * arguments»</em>, that's why \c ITK_ASSOCIATE() is not used with \c char
   * nor \c short.
   *
   * Then, we let the compiler choose the right \c Check() overload. From here,
   * it'll know which \c Detail::Identity<> specialization is returned.
   * \c sizeof can thus be used to extract the size of the specialization.
   *
   * Finally, \c Details::SizeToType<> returns the type that holds the result
   * of `a+b`.
   */
  using Type = typename Details::SizeToType<sizeof Check(a + b, 0), TA, TB>::Type;
};
} // end namespace mpl

/// \endcond
} // end namespace itk

#endif // itkPromoteType_h
